﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;
using System.IO;

using WebServer.Modules;

namespace WebServer
{
	/// <summary>
	/// Exposes advanced methods for manipulating string objects
	/// </summary>
	public static class StringUtility
	{
		/// <summary>
		/// Exposes advanced methods for converting string objects to various formats
		/// </summary>
		public static class Convert
		{
			/// <summary>
			/// Converts a base string object into the equivilent hash, code and unique ID values.
			/// </summary>
			/// <param name="fromBase">Base string object to convert.</param>
			/// <param name="hash">Hash value of the converted base string.</param>
			/// <param name="code">Code value of the converted base string.</param>
			/// <param name="uid">Unique ID value of the converted base string.</param>
			public static void ToUniqueInfo(string fromBase, out string hash, out string code, out long uid)
			{
				hash = ToHash(fromBase);
				code = ToCode(hash);
				uid = ToInt64(code);

				//Console.WriteLine("Input: {0}", fromBase);
				//Console.WriteLine("Out Hash: {0}", hash);
				//Console.WriteLine("Out Code: {0}", code);
				//Console.WriteLine("Out UID: {0:#,#}", uid);
			}

			/// <summary>
			/// Converts the given string object into the equivilent MD5 hash value.
			/// </summary>
			/// <param name="str">String object to convert.</param>
			/// <returns>The MD5 hash value equivilent to the given string object.</returns>
			public static string ToHash(string str)
			{
				return ToHash(str, CryptoHashType.MD5);
			}

			/// <summary>
			/// Converts the given string object into the given hash-type equivilent hash value.
			/// </summary>
			/// <param name="str">String object to convert.</param>
			/// <param name="type">The hashing type to use.</param>
			/// <returns>The given hash-type equivilent hash value to the given string object.</returns>
			public static string ToHash(string str, CryptoHashType type)
			{
				if (type == CryptoHashType.SHA1)
					return Cryptography.SHA1Hash(str);

				return Cryptography.MD5Hash(str);
			}

			/// <summary>
			/// Converts the given string object into the equivilent code value. 
			/// This method utilizes the Alphabet character mapping class.
			/// </summary>
			/// <param name="fromBase">String object to convert.</param>
			/// <returns>The code value equivilent to the given string object.</returns>
			public static string ToCode(string fromBase)
			{
				StringBuilder code = new StringBuilder();

				char[] chars = fromBase.ToCharArray();

				for (int i = 0; i < chars.Length && i < 20; i++)
				{
					char c = chars[i];
					int alphaIndex = Alphabet.GetAlphaIndex(c);

					if (alphaIndex < Alphabet.A || alphaIndex > Alphabet.Z)
					{
						if (Char.IsNumber(c))
							code.Append(c);
					}
					else
					{
						code.Append(alphaIndex);
					}
				}

				return code.ToString();
			}

			/// <summary>
			/// Attempts to convert the given string to the Int64 value equivilent.
			/// </summary>
			/// <param name="str">String object to convert.</param>
			/// <returns>The Int64 value equivilent to the given string object.</returns>
			public static long ToInt64(string str)
			{
				try
				{
					return System.Convert.ToInt64(str);
				}
				catch
				{
					return 0;
				}
			}

			/// <summary>
			/// Attempts to convert the given string to the Int32 value equivilent.
			/// </summary>
			/// <param name="str">String object to convert.</param>
			/// <returns>The Int32 value equivilent to the given string object.</returns>
			public static int ToInt32(string str)
			{
				try
				{
					return System.Convert.ToInt32(str);
				}
				catch
				{
					return 0;
				}
			}

			/// <summary>
			/// Converts the given byte data into the string value equivilent.
			/// </summary>
			/// <param name="data">Byte data to convert.</param>
			/// <returns>The string value of the converted byte data.</returns>
			public static string ToString(byte[] data)
			{
				StringBuilder sb = new StringBuilder(data.Length);

				for (int i = 0; i < data.Length; i++)
				{
					sb.Append(ToString(data[i]));
				}

				return sb.ToString();
			}

			/// <summary>
			/// Converts the given byte data into the string value equivilent.
			/// </summary>
			/// <param name="b">Byte data to convert.</param>
			/// <returns>The string value of the converted byte data.</returns>
			public static string ToString(byte b)
			{
				return ToString((char)b);
			}

			/// <summary>
			/// Converts the given char into the string value equivilent.
			/// </summary>
			/// <param name="c">Char object to convert.</param>
			/// <returns>The string value of the converted char object.</returns>
			public static string ToString(char c)
			{
				return c.ToString();
			}

			/// <summary>
			/// Converts the given integer into the hexidecimal string value equivilent.
			/// </summary>
			/// <param name="input">Int32 value to convert.</param>
			/// <param name="length">The length of the hexidecimal output, also known as the zero-offset. 
			/// Eg: A length of 6 will produce 0x000000</param>
			/// <returns>The hexidecimal string value of the converted Int32 value.</returns>
			public static string ToHexidecimal(int input, int length)
			{
				return String.Format("0x{0:X{1}}", input, length);
			}
		}

		/// <summary>
		/// Exposes a simple interface for alpha-numeric character-mapping.
		/// </summary>
		public static class Alphabet
		{
			private static char[] _Chars = new char[]
			{
				'A','B','C','D','E','F','G',
				'H','I','J','K','L','M','N',
				'O','P','Q','R','S','T','U',
				'V','W','X','Y','Z'
			};

			/// <summary>
			/// Gets an array of alpha-characters in the order that they appear in the common English alphabet.
			/// </summary>
			public static char[] Chars { get { return _Chars; } }

			/// <summary>
			/// Represents the index at which the character is found in the common English alphabet. 
			/// 1 to 26 respectively.
			/// </summary>
			public const int
				A = 1, B = 2, C = 3, D = 4,
				E = 5, F = 6, G = 7, H = 8,
				I = 9, J = 10, K = 11, L = 12,
				M = 13, N = 14, O = 15, P = 16,
				Q = 17, R = 18, S = 19, T = 20,
				U = 21, V = 22, W = 23, X = 24,
				Y = 25, Z = 26;

			/// <summary>
			/// Gets the char value of the character found at the specified index in the common English alphabet. 
			/// If
			/// </summary>
			/// <param name="alphaIndex">The index of the character to lookup in the common English alphabet.</param>
			/// <returns>The char object for the given char alphabet index. 
			/// If the index is out of range, the index will be cast as a char and returned.</returns>
			public static char GetChar(int alphaIndex)
			{
				char c = ' ';

				if (alphaIndex < A || alphaIndex > Z)
					c = (char)alphaIndex;
				else
					c = _Chars[alphaIndex];

				return c;
			}

			/// <summary>
			/// Gets the index of the given character in relation to the position in the common English alphabet.
			/// </summary>
			/// <param name="c">Char object to check.</param>
			/// <returns>The index of the given character in relation to the position in the common English alphabet.</returns>
			public static int GetAlphaIndex(char c)
			{
				for (int i = A; i < Z; i++)
				{
					if (_Chars[i - 1] == c)
						return i;
				}

				return 0;
			}
		}

		/// <summary>
		/// Provides the char-representation of the selected index integer value. 
		/// 0 to 9 respectively.
		/// </summary>	
		public static char[] Numerics = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

		/// <summary>
		/// Provides the char-representation of the selected index integer value as the equivilent alph-character at the index of the common English alphabet. 
		/// 0 to 9 translates to A to J respectively.
		/// </summary>
		public static char[] NumericReplacers = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' };

		/// <summary>
		/// Exposes a simple interface for special character-mapping
		/// </summary>
		public static class SpecialChars
		{
			private static char[] _Chars = new char[] 
			{ 
				'#', '!', '"', '£', '$', '%', '^', '&', '*','(', ')', '-', '[', ']', '{', '}',
				':', ';', '@', '\'', '~', '`', '¬', '¦', '\\', '/','|', '?', '<', '>', ',', '.'
			};

			/// <summary>
			/// Gets a list of unique and special characters.
			/// </summary>
			public static char[] Chars { get { return _Chars; } }

			/// <summary>
			/// Determines whether the given char is a special character.
			/// </summary>
			/// <param name="c">The character to check.</param>
			/// <returns>True if the char is a special character.</returns>
			public static bool IsSpecialChar(char c)
			{
				for (int i = 0; i < _Chars.Length; i++)
				{
					if (c == _Chars[i])
						return true;
				}

				return false;
			}

			/// <summary>
			/// Gets the char object for the given special char index.
			/// </summary>
			/// <param name="charIndex">The index of the special character in the Chars array.</param>
			/// <returns>The char object for the given special char index. 
			/// If the index is out of range, the index will be cast as a char and returned.</returns>
			public static char GetChar(int charIndex)
			{
				char c = ' ';

				if (charIndex < 0 || charIndex > _Chars.Length)
					c = (char)charIndex;
				else
					c = _Chars[charIndex];

				return c;
			}

			/// <summary>
			/// Gets the total number of chars available.
			/// </summary>
			public static int Count { get { return Chars.Length; } }

			/// <summary>
			/// Represents the char object equivilent of this special char
			/// </summary>
			public const char
				Hash = '#',
				ExclamationPoint = '!',
				DoubleQuote = '"',
				Pound = '£',
				Dollar = '$',
				Percentage = '%',
				PowerOf = '^',
				Ampersand = '&',
				Asterisk = '*',
				OpenBracket = '(',
				CloseBracket = ')',
				Dash = '-',
				OpenSquareBracket = '[',
				CloseSquareBracket = ']',
				OpenCurlyBracket = '{',
				CloseCurlyBracket = '}',
				Colon = ':',
				SemiColon = ';',
				At = '@',
				SingleQuote = '\'',
				Approximate = '~',
				LiteralSingleQuote = '`',
				EyeBrow = '¬',
				SplitSeperator = '¦',
				BackSlash = '\\',
				ForwardSlash = '/',
				Seperator = '|',
				QuestionMark = '?',
				LeessThan = '<',
				GreaterThan = '>',
				Comma = ',',
				FullStop = '.';
		}

		/// <summary>
		/// Provides a C# Key-Word string-representation list.
		/// </summary>
		public static class KeyWords
		{
			private static string[] _Words = new string[]
			{
				"foreach","abstract", "event", "new", "struct",
				"as", "explicit", "null", "switch", "base", "extern", "object",
				"this", "bool", "false", "operator", "throw", "break", "finally",
				"out", "true", "byte", "fixed", "override", "try", "case", "float",
				"params", "typeof", "catch", "for", "private", "uint", "char",
				 "protected", "ulong", "checked", "goto", "public",
				"unchecked", "class", "if", "readonly", "unsafe", "const",
				"implicit", "ref", "ushort", "continue", "in", "return", "using",
				"decimal", "int", "sbyte", "virtual", "default", "interface",
				"sealed", "volatile", "delegate", "internal", "short", "void",
				"do", "is", "sizeof", "while", "double", "lock", "stackalloc",
				"else", "long", "static", "enum", "namespace", "string"
			};

			/// <summary>
			/// Gets a complete list of all C# keywords.
			/// </summary>
			public static string[] Words { get { return _Words; } }

			/// <summary>
			/// Gets the total number of keywords available.
			/// </summary>
			public static int Count { get { return Words.Length; } }

			/// <summary>
			/// Represents the C# string object equivilent of this keyword
			/// </summary>
			public const string
				ForEach = "foreach", Protected = "protected",
				Volatile = "volatile", Delegate = "delegate",
				String = "string", NameSpace = "namespace",
				Sealed = "sealed", Interface = "interface",
				Class = "class", Unchecked = "unchecked",
				UShort = "ushort", Continue = "continue",
				Virtual = "virtual", Default = "default",
				Lock = "lock", StackAlloc = "stackalloc",
				Event = "event", Abstract = "abstract",
				False = "false", Operator = "operator",
				Fixed = "fixed", Override = "override",
				Short = "short", Internal = "internal",
				Null = "null", Explicit = "explicit",
				ULong = "ulong", Checked = "checked",
				Using = "using", Decimal = "decimal",
				Extern = "extern", Object = "object",
				Void = "void", Do = "do", As = "as",
				While = "while", Double = "double",
				Float = "float", Params = "params",
				Catch = "catch", TypeOf = "typeof",
				Const = "const", Unsafe = "unsafe",
				Ref = "ref", Implicit = "implicit",
				GoTo = "goto", Public = "public",
				If = "if", ReadOnly = "readonly",
				Throw = "throw", Break = "break",
				Out = "out", Finally = "finally",
				Base = "base", Switch = "switch",
				For = "for", Private = "private",
				Static = "static", Enum = "enum",
				New = "new", Struct = "struct",
				This = "this", Bool = "bool",
				True = "true", Byte = "byte",
				UInt = "uint", Char = "char",
				In = "in", Return = "return",
				Int = "int", SByte = "sbyte",
				Is = "is", SizeOf = "sizeof",
				Else = "else", Long = "long",
				Try = "try", Case = "case";
		}

		/// <summary>
		/// Parses the given string value and strips all special character and C# code keywords.
		/// </summary>
		/// <param name="value">String value to parse and strip.</param>
		/// <param name="capWords">Determines whether to set the first char of each word within the string to an uppercase char.</param>
		/// <returns>The parsed and stripped string value.</returns>
		public static string SafeCodeString(string value, bool capWords)
		{
			return SafeCodeString(value, capWords, 1);
		}

		/// <summary>
		/// Parses the given string value and strips all special character and C# code keywords.
		/// </summary>
		/// <param name="value">String value to parse and strip.</param>
		/// <param name="capWords">Determines whether to set the first char of each word within the string to an uppercase char.</param>
		/// <param name="minLength">The minimum length that the string value can be to be parsed and stripped.</param>
		/// <returns>The parsed and stripped string value. If length is less than the min length, an empty string will be returned.</returns>
		public static string SafeCodeString(string value, bool capWords, int minLength)
		{
			if (value == null)
				throw new ArgumentNullException("value");
			if (value.Length < minLength)
				return String.Empty;

			for (int i = 0; i < SpecialChars.Count; i++)
			{
				value = value.Replace(SpecialChars.GetChar(i).ToString(), "");
			}

			if (capWords)
				value = CapitalizeWords(value);

			value = value.Replace(' ', '_');
			value = value.TrimStart(new char[] { '_' });
			value = value.TrimEnd(new char[] { '_' });
			value = Regex.Replace(value, @"\W", "");
			value = value.Trim();

			for (int i = 0; i < KeyWords.Count; i++)
			{
				if (String.Compare(value, KeyWords.Words[i], false) == 0)
					value = value.Replace(KeyWords.Words[i], "");
			}

			for (int i = 0; i < Numerics.Length; i++)
			{
				if (value.StartsWith(Numerics[i].ToString(), false, CultureInfo.CurrentCulture))
				{
					value = value.Substring(1, value.Length - 1);
					value = NumericReplacers[i].ToString() + value;
					break;
				}
			}

			if (value.Length < minLength)
				return null;

			return value;
		}

		/// <summary>
		/// Capitalizes the first character of every word found within the string value.
		/// </summary>
		/// <param name="value">The string value to capitalize.</param>
		/// <returns>The capitalized string value.</returns>
		public static string CapitalizeWords(string value)
		{
			if (value == null)
				throw new ArgumentNullException("value");
			if (value.Length == 0)
				return value;

			StringBuilder sb = new StringBuilder(value.Length);

			// Upper the first char.
			sb.Append(char.ToUpper(value[0]));

			for (int i = 1; i < value.Length; i++)
			{
				// Get the current char.
				char c = value[i];

				// Upper if after a space.
				if (char.IsWhiteSpace(value[i - 1]))
					c = char.ToUpper(c);
				else
					c = char.ToLower(c);

				sb.Append(c);
			}

			value = sb.ToString();

			return value;
		}

		/// <summary>
		/// Determines whether a string consists entirely of uppercase characters.
		/// </summary>
		/// <param name="str">The string value to check.</param>
		/// <param name="digits">Determines whether the check should view numerical characters as uppercase.</param>
		/// <returns>True if uppercase; otherwise, false.</returns>
		public static bool IsUpperCase(string str, bool digits)
		{
			string pat = String.Format(@"[A-Z{0}]*", digits ? "0-9" : "");
			Regex regxp = new Regex(pat);

			if (regxp.IsMatch(str))
			{
				return true;
			}

			return false;
		}

		/// <summary>
		/// Determines whether a string consists entirely of lowercase characters.
		/// </summary>
		/// <param name="str">The string value to check.</param>
		/// <param name="digits">Determines whether the check should view numerical characters as lowercase.</param>
		/// <returns>True if lowercase; otherwise, false.</returns>
		public static bool IsLowerCase(string str, bool digits)
		{
			string pat = String.Format(@"[a-z{0}]*", digits ? "0-9" : "");
			Regex regxp = new Regex(pat);

			if (regxp.IsMatch(str))
			{
				return true;
			}

			return false;
		}

		public static string GetPercentage(int value, int total, int places)
		{
			Decimal percent = 0;
			String strplaces = new String('0', places);

			if (value == 0 || total == 0)
				percent = 0;
			else
			{
				percent = Decimal.Divide(value, total) * 100;

				if (places > 0)
					strplaces = "." + strplaces;
			}

			return percent.ToString("0" + strplaces);
		}

		public static string GetPercentage(long value, long total, int places)
		{
			Decimal percent = 0;
			String strplaces = new String('0', places);

			if (value == 0 || total == 0)
				percent = 0;
			else
			{
				percent = Decimal.Divide(value, total) * 100;

				if (places > 0)
					strplaces = "." + strplaces;
			}

			return percent.ToString("0" + strplaces);
		}

		public static string FormatTimeSpan(TimeSpan span)
		{
			string format = String.Empty;

			if (span.TotalMinutes > 0)
			{
				format =
					String.Format("{0:0} minute{1} and ", span.Minutes, span.Minutes != 1 ? "s" : "")
					+
					String.Format("{0:0} second{1}.", span.Seconds, span.Seconds != 1 ? "s" : "");
			}
			else if (span.TotalMinutes < 0 && span.TotalSeconds > 0)
			{
				format = String.Format("{0:0} second{1}.", span.Seconds, span.Seconds != 1 ? "s" : "");
			}

			return format;
		}
	}
}
